﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Objects;

using Pharmacy.Ultilities;
using System.Data.EntityClient;
using System.Linq.Expressions;
using System.Data.Objects.DataClasses;
using System.Collections;

namespace Pharmacy.Data
{
    public class ObjectContextSql : IObjectContext
    {
        const string UPDATEDDATEFIELD = "DateUpdated";
        const string UPDATEDUSERFIELD = "UserUpdated";
        const string CREATEDDATEFIELD = "DateCreated";
        const string CREATEDUSERFIELD = "UserCreated";
        const string OPTIMISTICLOCKFIELDNAME = "OptimisticLockField";
        readonly ObjectContext _context;
        private string userName = null;
        public ObjectContextSql(ObjectContext context)
        {
            _context = context;
        }
        public void Dispose()
        {
            _context.Dispose();
        }

        public IObjectSet<T> CreateObjectSet<T>() where T : class
        {
            return _context.CreateObjectSet<T>();            
        }

        public IObjectSet<T> CreateObjectSet<T>(MergeOption mergeOption) where T : class
        {
            var objectSet = _context.CreateObjectSet<T>();
            if (mergeOption == MergeOption.Overrive)
                objectSet.MergeOption = System.Data.Objects.MergeOption.OverwriteChanges;
            return objectSet;
        }
        
        public IEnumerable<T> ExecCommand<T>(string command,params object[] parameters)
        {
            return _context.ExecuteStoreQuery<T>(command, parameters).AsEnumerable();
        }

        public void ExecCommand(string command, params object[] parameters)
        {
            _context.ExecuteStoreCommand(command, parameters);
        }

        public void AttachAsModify<T>(T entity, params Expression<Func<T, object>>[] properties) where T : class
        {
            _context.Attach(entity as EntityObject);
            ObjectStateEntry objState = null;
            objState = _context.ObjectStateManager.GetObjectStateEntry(entity);
            if (properties == null)
            {
                //All
                objState.SetModified();
            }
            else
            {
                foreach (var property in properties)
                {
                    UnaryExpression express = (UnaryExpression)property.Body;
                    MemberExpression member = (MemberExpression)express.Operand;
                    string propertyName = member.Member.Name;
                    objState.SetModifiedProperty(propertyName);
                }
            }
        }

        public void SaveChanges()
        {
            _context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added | System.Data.EntityState.Modified).ForEach(entityState =>
            {
                var optimisticLockFieldProperty = entityState.Entity.GetType().GetProperty(OPTIMISTICLOCKFIELDNAME);
                var updatedUserFieldProperty = entityState.Entity.GetType().GetProperty(UPDATEDUSERFIELD);
                var updatedDateFieldProperty = entityState.Entity.GetType().GetProperty(UPDATEDDATEFIELD);
                var createUserFieldProperty = entityState.Entity.GetType().GetProperty(CREATEDUSERFIELD);
                var createDateFieldProperty = entityState.Entity.GetType().GetProperty(CREATEDDATEFIELD);

                if (entityState.State == System.Data.EntityState.Added)
                {
                    //Set 1 for New Object
                    if(optimisticLockFieldProperty != null)
                        optimisticLockFieldProperty.SetValue(entityState.Entity, 1, null);
                    createUserFieldProperty.SetValue(entityState.Entity, this.UserName, null);
                    createDateFieldProperty.SetValue(entityState.Entity, this.GetCurrentDateTime(), null);
                }

                if (entityState.State == System.Data.EntityState.Modified )
                {
                    //Set +1 for Modified Object
                    if (optimisticLockFieldProperty != null)
                    {
                        object currentOtimisticValue = optimisticLockFieldProperty.GetValue(entityState.Entity, null);
                        int newOptimisticValue = currentOtimisticValue == null? 1 : (int)currentOtimisticValue + 1;
                        optimisticLockFieldProperty.SetValue(entityState.Entity, newOptimisticValue, null);
                    }                    
                }

                if (updatedUserFieldProperty != null)
                    updatedUserFieldProperty.SetValue(entityState.Entity, this.UserName, null);
                if (updatedDateFieldProperty != null)
                    updatedDateFieldProperty.SetValue(entityState.Entity, this.GetCurrentDateTime(), null);                
            });
            _context.SaveChanges();            
        }

        public string UserName {
            get {
                return userName;
            }
            set {
                userName = value;
            }
        }

        public IObjectContext CreateNew(bool withNewConnection = true)
        {
            EntityConnection currentCnn = (EntityConnection)_context.Connection;
            EntityConnection newCnn = new EntityConnection(currentCnn.ConnectionString);
            ObjectContextSql adapter = new ObjectContextSql(Activator.CreateInstance(_context.GetType(), newCnn) as ObjectContext) { UserName = this.UserName };
            //Manual Open Connection
            adapter._context.Connection.Open();
            return adapter;
        }
        /// <summary>
        /// DiOMS Desktop using Currrent DateTime On Database Server
        /// </summary>
        /// <returns></returns>
        public DateTime GetCurrentDateTime()
        {
            return _context.CreateQuery<DateTime>("CurrentDateTime()").AsEnumerable().First();
        }

        /// <summary>
        /// Update an object in object context with data from data source
        /// </summary>
        /// <param name="refreshMode"></param>
        /// <param name="entity"></param>
        public void Reload(RefreshMode refreshMode, object entity)
        {
            _context.Refresh(refreshMode, entity);
        }

        /// <summary>
        /// Update a collection of objects in object context with data from data source
        /// </summary>
        /// <typeparam name="T">type of entity</typeparam>
        /// <param name="refreshMode"></param>
        /// <param name="entity">object is needed to reload</param>
        public void Reload(RefreshMode refreshMode, IEnumerable collection)
        {
            _context.Refresh(refreshMode, collection);
        }
    }
}
